'''
This module provides functionalities to create, parse and verifies torrent
files used in the simple-torrent system.

@author: Hatem Oraby
'''
from copy import copy
from math import ceil
import hashlib

class TorrentTags:
    fileName = "NAME"
    trackerUrl = "TRACKER"
    size = "SIZE"
    chunkSize = "CHUNK"
    fileHash = "FILEHASH"
    pieceHash = "HASH"
    order = [fileName,trackerUrl,size,chunkSize,fileHash,pieceHash]

    def __init__(self):
        self._actual = []
        
    def addAttr(self,attr): #Stupid Design
        if len(self._actual) < len(TorrentTags.order) -1:
            self._actual.append(attr)
        elif len(self._actual) == len(TorrentTags.order) -1:
            #It's first hash of the hash pieces
            li = [attr]
            self._actual.append([attr])
        else:
            #It's another hash
            #print self._actual[-1]
            #print self._actual
            self._actual[-1].append(attr)

    def getAttrs(self):
        return  self._actual


class TorrentObj():
    def __init__(self, fileName, trackerUrl, fileSize,
                 chunkSize, fileHash, piecesHash):
        #Document that You take everything as a string, Stupid Design
        self._fileName = fileName
        self._trackerUrl = trackerUrl
        try:
            self._fileSize = int(fileSize)
        except ValueError,e:
            print "Invalid file size: " + size
            raise ValueError,e
        try:
            self._chunkSize = int(chunkSize)
        except ValueError,e:
            err = "Invalid chunk size: " + size
            raise ValueError(e)
        self._fileHash = fileHash
        self._piecesHash = piecesHash
        #Do the math, NB:chunk size in KB while file size is reported in byte
        expectedPieces = int(ceil(self._fileSize/(self._chunkSize*1024.0)))
        if len(piecesHash) != expectedPieces:
            err = "Number of pieces hashes doesn't match expected number\n"
            err += "Expected: " + str(expectedPieces)
            err += "\tGot: " + str(len(piecesHash))
            raise ValueError(err) 
        else:
            self._piecesCount = expectedPieces

    def getFileName(self):
        return  self._fileName
    
    def getTrackerUrl(self):
        return  self._trackerUrl
    
    def getFileSize(self):
        return self._fileSize
    
    def getChunkSize(self):
        return self._chunkSize
    
    def getFileHash(self):
        return self._fileHash
    
    def getPiecesHash(self):
        return copy(self._piecesHash)#Don't mess with my original data
    
    def getPiecesCount(self):
        return  self._piecesCount

    def getAllAttrs(self):
        attrDict = {}
        attrDict[TorrentTags.fileName] = self.getFileName()
        attrDict[TorrentTags.trackerUrl] = self.getTrackerUrl()
        attrDict[TorrentTags.size] = self.getFileSize()
        attrDict[TorrentTags.chunkSize] = self.getChunkSize()
        attrDict[TorrentTags.fileHash] = self.getFileHash()
        attrDict[TorrentTags.pieceHash] = self.getPiecesCount()
        return attrDict

def createTorrent(fileName, path, trackerUrl, chunkSize, torrentName):
    fileObj = open(path + fileName, "rb")
    
    EOL = "\n"
    chunkSizeKb = chunkSize *1024 #Convert to KB
    fileNameTag = TorrentTags.fileName + ":" + fileName + EOL
    trackerUrlTag = TorrentTags.trackerUrl + ":" + trackerUrl + EOL
    piecesHash = []
    preHashTag = TorrentTags.pieceHash
    md5 = hashlib.md5()
    fileSize = 0
    while True:
        data = fileObj.read(chunkSizeKb)
        if not data:
            break
        pieceHash = hashlib.sha1(data).hexdigest()#Tell that you use hexdigest
        pieceHashTag = preHashTag +":" + pieceHash + EOL
        piecesHash.append(pieceHashTag)
        md5.update(data)
        fileSize += len(data)
    fileHash = md5.hexdigest()
    fileHashTag = TorrentTags.fileHash + ":" + fileHash + EOL
    fileSizeTag = TorrentTags.size + ":" + str(fileSize) + EOL
    chunkSizeTag = TorrentTags.chunkSize + ":" + str(chunkSize) + EOL
    
    order = [fileNameTag,trackerUrlTag,fileSizeTag,chunkSizeTag,
             fileHashTag] + piecesHash
    torrentFile = open(path + torrentName, "w")
    torrentFile.writelines(order)
    torrentFile.close()
    
def parseTorrent(torrentFile, path = ""):
    torrent = open(path + torrentFile, "r")
    lines = torrent.readlines()
    
    tags = TorrentTags()
    order = copy(TorrentTags.order)
    for line in lines:
        if line.strip() == "":
            continue
        tokens = line.split(":",1)#Make only one split
        if len(tokens) == 1:
            print "Invalid File Format"
            print "Didn't find ':' at line: ", len(order)
            return 
        keyword = tokens[0]
        keyword.upper()
        if keyword != order[0]:
            #Lets ease the debugging.
            if keyword in TorrentTags.order:
                print "Invalid Sequence found in torrent file."
                print "Expected: " + order[0] + "\tFound: " + keyword
            else:
                print "Unknown Tag found in torrent file"
                print "Found: " + keyword
            return 
        else:
            #I've found tag that was expected according to sequence
            if len(order) != 1:# #More stupid design
                #It's piece Hash, it can get repeated any number of times
                order.pop(0)#Forward to the next tag.
        param = tokens[1]
        param = param.strip()
        tags.addAttr(param)
    #end of loop
    if len(order) != 1:
        print "Incomplete Torrent File"
        return
    torrentData = tags.getAttrs()
    #print  torrentData
    return TorrentObj(*torrentData)




def verifyTorrent(torrObj, torrentName, path = ""):
    #torrObj = TorrentObj(); raise "Delete Line"
    torrData = torrObj.getAllAttrs()
    calcTorrentObj = parseTorrent(torrentName, path)
    calcData = calcTorrentObj.getAllAttrs()
    
    for element in calcData.keys(): #We can do map
        if calcData[element] != torrData[element]:
            print "Wrong Element: ", element
            print "Expected: " , calcData[element],
            print "\tGot: ", torrData[element]
            return False
    
    return  True
        

def main():
    fileName = "Camel - Lawrence.flv"
    filePath = ""
    outputFile = "Lawrence.tor"
    createTorrent(fileName, filePath, "127.0.0.1:2000/CamelLawrence.flv",512, 
                  outputFile)
   
    x = parseTorrent(outputFile)
    y = copy(x)
    assert verifyTorrent(y, outputFile, filePath),True
    fileName2 = "torrentFile.py"
    outputFile2 = "torr2.tor"
    createTorrent(fileName2,filePath, "", 512, outputFile2)
    x2 = parseTorrent(outputFile2)
    assert  verifyTorrent(x2, outputFile2, filePath), False
    #Clean up
    import os
    os.remove(outputFile)
    os.remove(outputFile2)
    
if __name__ == '__main__':
    main()